/*  Starshatter OpenSource Distribution
    Copyright (c) 1997-2004, Destroyer Studios LLC.
    All Rights Reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice,
      this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice,
      this list of conditions and the following disclaimer in the documentation
      and/or other materials provided with the distribution.
    * Neither the name "Destroyer Studios" nor the names of its contributors
      may be used to endorse or promote products derived from this software
      without specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.

    SUBSYSTEM:    nGenEx.lib
    FILE:         Color.cpp
    AUTHOR:       John DiCamillo


    OVERVIEW
    ========
    Universal Color Format class
*/

#include "MemDebug.h"
#include "Color.h"
#include "Video.h"
#include "PCX.h"
#include "Fix.h"

// +--------------------------------------------------------------------+

void Print(const char* fmt, ...);

Color          Color::White      = Color(255,255,255);
Color          Color::Black      = Color(  0,  0,  0);
Color          Color::Gray       = Color(128,128,128);
Color          Color::LightGray  = Color(192,192,192);
Color          Color::DarkGray   = Color( 64, 64, 64);
Color          Color::BrightRed  = Color(255,  0,  0);
Color          Color::BrightBlue = Color(  0,  0,255);
Color          Color::BrightGreen= Color(  0,255,  0);
Color          Color::DarkRed    = Color(128,  0,  0);
Color          Color::DarkBlue   = Color(  0,  0,128);
Color          Color::DarkGreen  = Color(  0,128,  0);
Color          Color::Yellow     = Color(255,255,  0);
Color          Color::Cyan       = Color(  0,255,255);
Color          Color::Magenta    = Color(255,  0,255);
Color          Color::Tan        = Color(180,150,120);
Color          Color::Brown      = Color(128,100, 80);
Color          Color::Violet     = Color(128,  0,128);
Color          Color::Orange     = Color(255,150, 20);

bool           Color::standard_format = false;
int            Color::texture_alpha_level = 0;
ColorFormat    Color::format     = ColorFormat(256);
ColorFormat    Color::texture_format[4]  = { ColorFormat(256), ColorFormat(256), ColorFormat(256), ColorFormat(256) };
PALETTEENTRY   Color::palette[256];
BYTE           Color::table[32768];
double         Color::fade = 1.0;
Color          Color::fade_color;
Video*         Color::video = 0;
DWORD          ColorIndex::texture_palettes[4][256];
DWORD          ColorIndex::unfaded_palette[256];
DWORD          ColorIndex::formatted_palette[256];
DWORD          ColorIndex::shade_table[256*256];
BYTE           ColorIndex::blend_table[256*256];
DWORD*         ColorIndex::texture_palette = ColorIndex::texture_palettes[0];

// +--------------------------------------------------------------------+

Color::Color(BYTE index)
{
    PALETTEENTRY* p = &palette[index];

    Set(p->peRed, p->peGreen, p->peBlue);
}

// +--------------------------------------------------------------------+

Color&
Color::operator+=(const Color& c)
{
    int  r = Red()   + c.Red();     if (r > 255) r = 255;
    int  g = Green() + c.Green();   if (g > 255) g = 255;
    int  b = Blue()  + c.Blue();    if (b > 255) b = 255;

    Set((BYTE) r, (BYTE) g, (BYTE) b);
    return *this;
}

Color
Color::operator+(DWORD d) const
{
    int r = Red()   + ((d & RMask) >> RShift);   if (r > 255) r = 255;
    int g = Green() + ((d & GMask) >> GShift);   if (g > 255) g = 255;
    int b = Blue()  + ((d & BMask) >> BShift);   if (b > 255) b = 255;

    return Color((BYTE) r,(BYTE) g,(BYTE) b);
}

// +--------------------------------------------------------------------+

Color
Color::operator+(const Color& c) const
{
    float src_alpha = c.fAlpha();
    float dst_alpha = 1.0f - src_alpha;

    BYTE  r = (BYTE)((fRed()  *dst_alpha + c.fRed()  *src_alpha)*255.0f);
    BYTE  g = (BYTE)((fGreen()*dst_alpha + c.fGreen()*src_alpha)*255.0f);
    BYTE  b = (BYTE)((fBlue() *dst_alpha + c.fBlue() *src_alpha)*255.0f);

    return Color(r, g, b);
}

// +--------------------------------------------------------------------+

Color
Color::operator*(const Color& c) const
{
    BYTE  r = (BYTE) ((fRed()   * c.fRed())   *255.0f);
    BYTE  g = (BYTE) ((fGreen() * c.fGreen()) *255.0f);
    BYTE  b = (BYTE) ((fBlue()  * c.fBlue())  *255.0f);

    return Color(r, g, b);
}

// +--------------------------------------------------------------------+

Color
Color::operator*(double scale) const
{
    int r = fast_f2i(Red()   * scale); if (r > 255) r = 255;
    int g = fast_f2i(Green() * scale); if (g > 255) g = 255;
    int b = fast_f2i(Blue()  * scale); if (b > 255) b = 255;
    int a = fast_f2i(Alpha() * scale); if (a > 255) a = 255;

    return Color((BYTE) r, (BYTE) g, (BYTE) b, (BYTE) a);
}

Color
Color::dim(double scale) const
{
    int r = fast_f2i(Red()   * scale);
    int g = fast_f2i(Green() * scale);
    int b = fast_f2i(Blue()  * scale);

    return Color((BYTE) r, (BYTE) g, (BYTE) b, (BYTE) Alpha());
}

// +--------------------------------------------------------------------+

DWORD
Color::Formatted() const
{
    if (format.pal) {
        return Index();
    }

    else {
        if (fade != 1.0) {
            double step = (1.0 - fade);

            DWORD r = ((int) ((fRed()   - (fRed()   - fade_color.fRed())  * step)*255.0)) >> format.rdown;
            DWORD g = ((int) ((fGreen() - (fGreen() - fade_color.fGreen())* step)*255.0)) >> format.gdown;
            DWORD b = ((int) ((fBlue()  - (fBlue()  - fade_color.fBlue()) * step)*255.0)) >> format.bdown;
            DWORD a = Alpha()>>format.adown;

            return (r<<format.rshift)|(g<<format.gshift)|(b<<format.bshift)|(a<<format.ashift);

        }

        else if (standard_format) {
            return rgba;
        }

        else {
            DWORD r = Red()  >>format.rdown;
            DWORD g = Green()>>format.gdown;
            DWORD b = Blue() >>format.bdown;
            DWORD a = Alpha()>>format.adown;
            
            return (r<<format.rshift)|(g<<format.gshift)|(b<<format.bshift)|(a<<format.ashift);
        }
    }
}

// +--------------------------------------------------------------------+

Color
Color::Faded() const
{
    if (fade != 1.0) {
        double step = (1.0 - fade);

        DWORD r = ((int) ((fRed()   - (fRed()   - fade_color.fRed())  * step)*255.0));
        DWORD g = ((int) ((fGreen() - (fGreen() - fade_color.fGreen())* step)*255.0));
        DWORD b = ((int) ((fBlue()  - (fBlue()  - fade_color.fBlue()) * step)*255.0));
        DWORD a = Alpha();

        return Color((BYTE) r, (BYTE) g, (BYTE) b, (BYTE) a);

    }

    else {
        return *this;
    }
}

// +--------------------------------------------------------------------+

DWORD
Color::Unfaded() const
{
    if (standard_format) {
        return rgba;
    }

    if (format.pal) {
        return Index();
    }
    else {
        DWORD r = Red()  >>format.rdown;
        DWORD g = Green()>>format.gdown;
        DWORD b = Blue() >>format.bdown;
        DWORD a = Alpha()>>format.adown;
        
        return (r<<format.rshift)|(g<<format.gshift)|(b<<format.bshift)|(a<<format.ashift);
    }
}

// +--------------------------------------------------------------------+

DWORD
Color::TextureFormat(int keep_alpha) const
{
    if (texture_format[texture_alpha_level].pal) {
        return Index();
    }
    else if (rgba == 0) {
        return 0;
    }
    else {
        ColorFormat& tf = texture_format[texture_alpha_level];
        
        DWORD r = Red();
        DWORD g = Green();
        DWORD b = Blue();
        DWORD a = 0;
        
        if (keep_alpha) {
            a = Alpha()>>tf.adown;
        }

        else if (texture_alpha_level == 1) {
            // transparent:
            a = 255>>tf.adown;
        }
        
        else if (texture_alpha_level == 2) {
            // translucent:
            if (r || g || b)
            a = ((r+g+b+255)>>2)>>tf.adown;
        }

        r = r >>tf.rdown;
        g = g >>tf.gdown;
        b = b >>tf.bdown;

        return (r<<tf.rshift)|(g<<tf.gshift)|(b<<tf.bshift)|(a<<tf.ashift);
    }
}

// +--------------------------------------------------------------------+

Color
Color::ShadeColor(int shade) const
{
    double   fr = fRed(),      sr = fr;
    double   fg = fGreen(),    sg = fg;
    double   fb = fBlue(),     sb = fb;
    double   range = SHADE_LEVELS;

    // first shade:
    if (shade < SHADE_LEVELS) {          // shade towards black
        sr = fr * (shade/range);
        sg = fg * (shade/range);
        sb = fb * (shade/range);
    }
    else if (shade > SHADE_LEVELS) {     // shade towards white
        double step = (shade - range)/range;
        
        sr = fr - (fr - 1.0) * step;
        sg = fg - (fg - 1.0) * step;
        sb = fb - (fb - 1.0) * step;
    }

    return Color((BYTE) (sr*255.0), (BYTE) (sg*255.0), (BYTE) (sb*255.0), (BYTE) Alpha());
}

// +--------------------------------------------------------------------+

DWORD
Color::Shaded(int shade) const
{
    return ShadeColor(shade).Formatted();
}

// +--------------------------------------------------------------------+

Color
Color::Unformat(DWORD formatted_color)
{
    if (format.pal) {
        return Color((BYTE) formatted_color);
    }
    else if (standard_format) {
        Color c;
        c.Set(formatted_color);
        return c;
    }
    else {
        BYTE r = (BYTE) ((formatted_color & format.rmask)>>format.rshift) << format.rdown;
        BYTE g = (BYTE) ((formatted_color & format.gmask)>>format.gshift) << format.gdown;
        BYTE b = (BYTE) ((formatted_color & format.bmask)>>format.bshift) << format.bdown;
        BYTE a = (BYTE) ((formatted_color & format.amask)>>format.ashift) << format.adown;
        
        return Color(r,g,b,a);
    }
}

// +--------------------------------------------------------------------+

Color
Color::Scale(const Color& c1, const Color& c2, double scale)
{
    BYTE r = (BYTE) ((c1.fRed()   + (c2.fRed()   - c1.fRed()  )*scale) * 255);
    BYTE g = (BYTE) ((c1.fGreen() + (c2.fGreen() - c1.fGreen())*scale) * 255);
    BYTE b = (BYTE) ((c1.fBlue()  + (c2.fBlue()  - c1.fBlue() )*scale) * 255);
    BYTE a = (BYTE) ((c1.fAlpha() + (c2.fAlpha() - c1.fAlpha())*scale) * 255);

    return Color(r,g,b,a);
}

// +--------------------------------------------------------------------+

DWORD
Color::FormattedBlend(DWORD c1, DWORD c2)
{
    if (format.pal) {
        return ColorIndex::blend_table[(BYTE) c1 * 256 + (BYTE) c2];
    }
    else {
        ColorFormat& cf = format;

        DWORD r = (c1 & cf.rmask) + (c2 & cf.rmask);
        DWORD g = (c1 & cf.gmask) + (c2 & cf.gmask);
        DWORD b = (c1 & cf.bmask) + (c2 & cf.bmask);

        if (r & ~cf.rmask) r = cf.rmask;
        if (g & ~cf.gmask) g = cf.gmask;
        if (b & ~cf.bmask) b = cf.bmask;

        return (DWORD) (r|g|b);
    }
}

// +--------------------------------------------------------------------+

void
Color::UseVideo(Video* v)
{
    video = v;
}

// +--------------------------------------------------------------------+

void
Color::UseFormat(const ColorFormat& cf)
{
    format = cf;

    if (format.rmask == RMask && format.gmask == GMask && format.bmask == BMask)
    standard_format = true;
    else
    standard_format = false;

    if (cf.pal) {
        for (int i = 0; i < 256; i++) {
            ColorIndex::formatted_palette[i]   = i;
            ColorIndex::unfaded_palette[i]     = i;
        }
    }
    else {
        double old_fade = fade;

        for (int i = 0; i < 256; i++) {
            ColorIndex::formatted_palette[i] = Color(i).Formatted();

            fade = 1.0;
            ColorIndex::unfaded_palette[i]   = Color(i).Formatted();
            fade = old_fade;
        }
    }
}

// +--------------------------------------------------------------------+

void
Color::UseTextureFormat(const ColorFormat& cf, int alpha_level)
{
    texture_format[alpha_level] = cf;

    if (cf.pal) {
        for (int i = 0; i < 256; i++) {
            ColorIndex::texture_palettes[alpha_level][i] = i;
        }
    }
    else {
        double old_fade = fade;

        for (int i = 0; i < 256; i++) {
            int old_texture_alpha_level = texture_alpha_level;
            texture_alpha_level = alpha_level;
            ColorIndex::texture_palettes[alpha_level][i] = Color(i).TextureFormat();
            texture_alpha_level = old_texture_alpha_level;
        }
    }
}

// +--------------------------------------------------------------------+

void
Color::WithTextureFormat(int alpha_level)
{
    texture_alpha_level = alpha_level;
    ColorIndex::texture_palette = ColorIndex::texture_palettes[alpha_level];
}

// +--------------------------------------------------------------------+

static BYTE MatchRGB(PALETTEENTRY* pal, BYTE r, BYTE g, BYTE b)
{
    double mindist = 100000000.0;
    BYTE   match   = 0;

    for (int i = 0; i < 256; i++) {
        PALETTEENTRY* p = pal++;

        double dr = p->peRed    - r;
        double dg = p->peGreen  - g;
        double db = p->peBlue   - b;
        
        double d = (dr*dr) + (dg*dg) + (db*db);

        if (d < mindist) {
            mindist = d;
            match   = i;
            
            if (d < 1.0)
            return match;
        }
    }

    return match;
}

// +--------------------------------------------------------------------+

void
Color::SetPalette(PALETTEENTRY* pal, int palsize, BYTE* invpal)
{
    for (int i = 0; i < palsize; i++)
    palette[i] = pal[i];

    if (invpal) {
        for (int i = 0; i < 32768; i++)
        table[i] = invpal[i];
    }
    else {
        for (int i = 0; i < 32768; i++) {
            BYTE r = (i >> 10) & 0x1f;
            BYTE g = (i >>  5) & 0x1f;
            BYTE b = (i      ) & 0x1f;
            
            Color c(r<<3, g<<3, b<<3);
            
            table[i] = MatchRGB(palette, r<<3, g<<3, b<<3);
        }
    }

    // set up formatted palette:
    UseFormat(format);

    for (int i = 0; i < 4; i++)
    UseTextureFormat(texture_format[i], i);

    // set up shade table:
    double old_fade = fade;
    fade = 1.0;
    BuildShadeTable();
    fade = old_fade;

    // and blend table:
    BuildBlendTable();
}

// +--------------------------------------------------------------------+

void
Color::SavePalette(const char* basename)
{
    char  filename[256];

    sprintf_s(filename, "%s.ipl", basename);
    FILE* f;
    fopen_s(&f, filename, "wb");
    if (f) {
        fwrite(table, sizeof(table), 1, f);
        fclose(f);
    }
}

// +--------------------------------------------------------------------+

void
Color::SetFade(double f, Color c, int build_shade)
{
    static int shade_built = 0;

    if (fade == f && fade_color == c && (build_shade ? shade_built : 1))
    return;

    fade       = f;
    fade_color = c;

    // set up formatted palette:
    UseFormat(format);

    // if this is a paletted video mode,
    // modify the video palette as well:
    if (format.pal && video) {
        PALETTEENTRY fade_palette[256];
        
        double step = (1.0 - fade);
        for (int i = 0; i < 256; i++) {
            PALETTEENTRY& entry = fade_palette[i];
            ColorIndex    c     = ColorIndex(i);

            entry.peRed   = ((int) ((c.fRed()   - (c.fRed()   - fade_color.fRed())  * step)*255.0));
            entry.peGreen = ((int) ((c.fGreen() - (c.fGreen() - fade_color.fGreen())* step)*255.0));
            entry.peBlue  = ((int) ((c.fBlue()  - (c.fBlue()  - fade_color.fBlue()) * step)*255.0));
            entry.peFlags = 0;
        }
    }

    // otherwise, we need to re-compute 
    // the shade table:
    else {
        if (build_shade) {
            BuildShadeTable();
            shade_built = 1;
        }
        else {
            shade_built = 0;
        }
    }
}

// +--------------------------------------------------------------------+

void
Color::BuildShadeTable()
{
    for (int shade = 0; shade < SHADE_LEVELS*2; shade++)
    for (int index = 0; index < 256; index++)
    ColorIndex::shade_table[shade*256+index] = Color(index).Shaded(shade);
}

// +--------------------------------------------------------------------+

void
Color::BuildBlendTable()
{
    for (int src = 0; src < 256; src++) {
        for (int dst = 0; dst < 256; dst++) {
            ColorIndex src_clr = ColorIndex(src);
            ColorIndex dst_clr = ColorIndex(dst);
            
            int r = src_clr.Red()   + dst_clr.Red();
            int g = src_clr.Green() + dst_clr.Green();
            int b = src_clr.Blue()  + dst_clr.Blue();
            
            if (r>255) r=255;
            if (g>255) g=255;
            if (b>255) b=255;

            ColorIndex::blend_table[src*256+dst] = Color((BYTE)r,(BYTE)g,(BYTE)b).Index();
        }
    }
}

// +--------------------------------------------------------------------+

void
Color::SaveShadeTable(const char* basename)
{
    if (!format.pal)
    return;

    char filename[256];
    sprintf_s(filename, "%s_clut.pcx", basename);

    BYTE  clut[256*256];
    BYTE* pc = clut;
    int i;
    for (i = 0; i < SHADE_LEVELS*2; i++)
    for (int j = 0; j < 256; j++)
    *pc++ = (BYTE) ColorIndex::shade_table[i*256+j];

    for (; i < 256; i++)
    for (int j = 0; j < 256; j++)
    *pc++ = (BYTE) 0;

    PcxImage pcx(256, 256, clut, (BYTE*) palette);
    pcx.Save(filename);
}


// +--------------------------------------------------------------------+
// +--------------------------------------------------------------------+
// +--------------------------------------------------------------------+


ColorValue&
ColorValue::operator+=(const ColorValue& c)
{
    r += c.r;
    g += c.g;
    b += c.b;

    return *this;
}

ColorValue
ColorValue::operator+(const ColorValue& c) const
{
    float src_alpha = c.a;
    float dst_alpha = 1.0f - a;

    float fr = (r * dst_alpha) + (c.r * src_alpha);
    float fg = (g * dst_alpha) + (c.g * src_alpha);
    float fb = (b * dst_alpha) + (c.b * src_alpha);

    return ColorValue(fr, fg, fb);
}

ColorValue
ColorValue::operator*(const ColorValue& c) const
{
    return ColorValue(r*c.r, g*c.g, b*c.b);
}

ColorValue
ColorValue::operator*(double scale) const
{
    return ColorValue((float) (r*scale),
    (float) (g*scale),
    (float) (b*scale),
    (float) (a*scale));
}

ColorValue
ColorValue::dim(double scale) const
{
    return ColorValue((float) (r*scale),
    (float) (g*scale),
    (float) (b*scale));
}

// +--------------------------------------------------------------------+

inline BYTE bclamp(float x) { return (BYTE) ((x<0) ? 0 : (x>255) ? 255 : x); }

Color
ColorValue::ToColor() const
{
    return Color(bclamp(r * 255.0f), 
    bclamp(g * 255.0f),
    bclamp(b * 255.0f),
    bclamp(a * 255.0f));
}
